package com.hzya.frame;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hzya.frame.util.ApiTokenManager;
import com.hzya.frame.util.Constants;
import com.hzya.frame.util.CbsApiHandler;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.*;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.*;

@Tags({"cbs", "sm2", "api", "招商银行"})
@CapabilityDescription("调用招商银行CBS SM2加密接口的通用处理器。它负责管理Token并调用SM2Example类来执行核心的加解密和签名逻辑。")
@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
public class ZsyhCbsProcessor extends AbstractProcessor {

    // 静态代码块，在类加载时自动、安全地注册Bouncy Castle，避免环境问题
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    // --- 定义处理器属性 ---
    // API 业务接口相关属性
    public static final PropertyDescriptor PROP_TARGET_URL = new PropertyDescriptor.Builder().name("业务接口URL (TARGET_URL)").displayName("业务接口URL").description("需要请求的最终业务接口URL。").required(true).addValidator(StandardValidators.URL_VALIDATOR).build();

    // SM2 加解密相关属性
    public static final PropertyDescriptor PROP_BODY_ENCRYPTION_KEY = new PropertyDescriptor.Builder().name("平台公钥 (bodyEncryptionKey)").displayName("平台公钥").description("用于加密请求体的平台方公钥（Hex编码）。").required(true).sensitive(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();

    public static final PropertyDescriptor PROP_SIGN_PRIVATE_KEY = new PropertyDescriptor.Builder().name("企业签名私钥 (signEncryptionPrivateKey)").displayName("企业签名私钥").description("用于对请求进行签名的企业私钥（Hex编码）。").required(true).sensitive(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();

    public static final PropertyDescriptor PROP_BODY_DECRYPTION_KEY = new PropertyDescriptor.Builder().name("企业解密私钥 (bodyDecryptionKey)").displayName("企业解密私钥").description("用于解密响应体的企业私钥（Hex编码）。").required(true).sensitive(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();

    // Token 管理器相关属性
    public static final PropertyDescriptor PROP_TOKEN_URL = new PropertyDescriptor.Builder().name("Token获取URL").displayName("Token获取URL").required(true).addValidator(StandardValidators.URL_VALIDATOR).build();
    public static final PropertyDescriptor PROP_REFRESH_URL = new PropertyDescriptor.Builder().name("Token刷新URL").displayName("Token刷新URL").required(true).addValidator(StandardValidators.URL_VALIDATOR).build();
    public static final PropertyDescriptor PROP_APP_ID = new PropertyDescriptor.Builder().name("App ID").displayName("App ID").required(true).sensitive(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
    public static final PropertyDescriptor PROP_APP_SECRET = new PropertyDescriptor.Builder().name("App Secret").displayName("App Secret").required(true).sensitive(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();

    // --- 处理器关系定义 ---
    public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description("请求成功且响应成功处理后的FlowFile。").build();
    public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure").description("处理过程中发生任何错误的FlowFile。").build();

    private List<PropertyDescriptor> descriptors;
    private Set<Relationship> relationships;

    @Override
    protected void init(final ProcessorInitializationContext context) {
        final List<PropertyDescriptor> descriptors = new ArrayList<>();
        descriptors.add(PROP_TARGET_URL);
        descriptors.add(PROP_BODY_ENCRYPTION_KEY);
        descriptors.add(PROP_SIGN_PRIVATE_KEY);
        descriptors.add(PROP_BODY_DECRYPTION_KEY);
        descriptors.add(PROP_TOKEN_URL);
        descriptors.add(PROP_REFRESH_URL);
        descriptors.add(PROP_APP_ID);
        descriptors.add(PROP_APP_SECRET);
        this.descriptors = Collections.unmodifiableList(descriptors);

        final Set<Relationship> relationships = new HashSet<>();
        relationships.add(REL_SUCCESS);
        relationships.add(REL_FAILURE);
        this.relationships = Collections.unmodifiableSet(relationships);
    }

    @Override
    public Set<Relationship> getRelationships() {
        return this.relationships;
    }

    @Override
    public final List<PropertyDescriptor> getSupportedPropertyDescriptors() {
        return this.descriptors;
    }

    @OnScheduled
    public void onScheduled(final ProcessContext context) {
        // 在处理器启动或配置更新时，调用一次ApiTokenManager的configure方法进行初始化
        getLogger().info("正在为招行CBS接口配置API Token管理器...");
        ApiTokenManager.getInstance().configure(context.getProperty(PROP_TOKEN_URL).getValue(), context.getProperty(PROP_REFRESH_URL).getValue(), context.getProperty(PROP_APP_ID).getValue(), context.getProperty(PROP_APP_SECRET).getValue(), getLogger());
    }


    @Override
    public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
        FlowFile flowFile = session.get();
        if (flowFile == null) {
            return;
        }

        // 使用Jackson库来处理JSON
        final ObjectMapper objectMapper = new ObjectMapper();

        // 用于累加所有分页查询结果的列表
        List<JsonNode> allRecords = new ArrayList<>();

        try {
            ObjectNode requestTemplate;
            try (InputStream in = session.read(flowFile)) {
                String initialRequestJson = IOUtils.toString(in, StandardCharsets.UTF_8);
                requestTemplate = (ObjectNode) objectMapper.readTree(initialRequestJson);
            }

            // 获取初始页码
            int currentPage = requestTemplate.has("currentPage") ? requestTemplate.get("currentPage").asInt() : 1;

            //开始分页循环
            while (true) {
                getLogger().info("正在请求第 {} 页数据...", currentPage);

                //更新请求模板中的当前页码
                requestTemplate.put("currentPage", currentPage);
                byte[] requestDataBytes = objectMapper.writeValueAsBytes(requestTemplate);

                //执行单次API请求
                byte[] decryptedResponseBytes;
                CloseableHttpClient client = HttpClients.custom().disableContentCompression().build();
                try {
                    String targetUrl = context.getProperty(PROP_TARGET_URL).getValue();
                    String bodyEncryptionKey = context.getProperty(PROP_BODY_ENCRYPTION_KEY).getValue();
                    String signPrivateKey = context.getProperty(PROP_SIGN_PRIVATE_KEY).getValue();
                    String bodyDecryptionKey = context.getProperty(PROP_BODY_DECRYPTION_KEY).getValue();

                    String bearerToken = ApiTokenManager.getInstance().getValidToken();
                    String token = bearerToken.startsWith(Constants.BEARER) ? bearerToken.substring(Constants.BEARER.length()) : bearerToken;

                    CbsApiHandler apiHandler = new CbsApiHandler(targetUrl, token, bodyEncryptionKey, signPrivateKey, bodyDecryptionKey, requestDataBytes);

                    HttpPost httpPost = apiHandler.setupRequest();
                    try (CloseableHttpResponse response = client.execute(httpPost)) {
                        if (response.getStatusLine().getStatusCode() >= 300) {
                            String errorBody = new String(IOUtils.toByteArray(response.getEntity().getContent()), StandardCharsets.UTF_8);
                            throw new IOException("HTTP请求失败，页码: " + currentPage + ", 状态码: " + response.getStatusLine().getStatusCode() + ", 详情: " + errorBody);
                        }
                        decryptedResponseBytes = apiHandler.handleResponse(response);
                    }
                } finally {
                    client.close();
                }

                //解析解密后的响应体
                if (decryptedResponseBytes == null || decryptedResponseBytes.length == 0) {
                    getLogger().info("第 {} 页响应体为空，认定为分页结束。", currentPage);
                    break;
                }

                JsonNode responseJson = objectMapper.readTree(new String(decryptedResponseBytes, StandardCharsets.UTF_8));

                // 检查业务返回码
                String code = responseJson.path("code").asText();
                if (!"0".equals(code)) {
                    throw new ProcessException("API业务错误，页码: " + currentPage + ", 错误码: " + code + ", 消息: " + responseJson.path("msg").asText());
                }

                //提取当页的记录列表
                JsonNode listNode = responseJson.path("data").path("list");

                //判断循环终止条件
                if (!listNode.isArray() || listNode.size() == 0) {
                    getLogger().info("第 {} 页返回的记录列表为空，认定为分页结束。", currentPage);
                    break; // 如果list为空或不存在，则结束循环
                }

                //累加结果
                for (JsonNode record : listNode) {
                    allRecords.add(record);
                }

                //准备请求下一页
                currentPage++;
            }

            //所有分页成功后，合并最终结果
            ObjectNode finalResult = objectMapper.createObjectNode();
            ArrayNode finalArray = objectMapper.valueToTree(allRecords);
            finalResult.set("allRecords", finalArray); // 将所有记录放入一个名为"allRecords"的数组中
            finalResult.put("totalFetched", allRecords.size());

            //将合并后的JSON写入FlowFile并路由到success
            flowFile = session.write(flowFile, out -> objectMapper.writerWithDefaultPrettyPrinter().writeValue(out, finalResult));
            flowFile = session.putAttribute(flowFile, "api.total.records.fetched", String.valueOf(allRecords.size()));
            session.transfer(flowFile, REL_SUCCESS);

        } catch (Exception e) {
            getLogger().error("调用招行CBS接口并处理分页时失败: {}", e.getMessage(), e);
            flowFile = session.putAttribute(flowFile, "api.error", e.getMessage());
            session.transfer(session.penalize(flowFile), REL_FAILURE);
        }
    }
}